<?php
class DB {
    
    private $db;
    
    public $rows_affected = 0;  //影响的记录行数
    public $insert_id     = 0;  //插入的记录ID
    public $num_rows      = 0;  //记录集总数
    
    /**
     * 最后一条查询的语句
     * @var string
     */
    public $last_query    = '';
    
    /**
     * 该页查询DB次数
     * @var int
     */
    public $num_queries = 0;
    
    /**
     * 所有执行过的sql语句
     * @var array
     */
    public $queries = array();
    
    private $last_result;       //最后一个记录集
    private $result;            //记录集暂存，see DB::query
    
    /**
     * 连接数据库
     * @param string $dbhost 数据库地址
     * @param string $dbname 数据库名称
     * @param string $dbuser 数据库登录用户名
     * @param string $dbpass 数据库登录密码
     */
    public function __construct($dbhost, $dbname, $dbuser, $dbpass) {
        $host = explode(':', $dbhost);
        if (count($host) == 1) {
            $host[1] = 3306;
        }
        $db = @new mysqli($host[0], $dbuser, $dbpass, $dbname, $host[1]);//mysql_connect( $dbhost, $dbuser, $dbpass );
        if ($db->connect_error) {
            die('Sorry, DB too tired.');
        }
        $db->set_charset('utf8');//mysql_set_charset('utf8', $this->db);
        $this->db = $db;
    }
    
    /**
     * 执行一个SQL查询
     * @param string $query 查询字符串
     * @return 返回记录集总数，或者影响的行数，或者是一个错误 false
     */
    public function query($query) {
        
        $return_val = 0;
        $this->flush();
        
        //记录查询语句
        $this->last_query = $query;

        $db = &$this->db;
        $result = @$db->query($query);//@mysql_query( $query, $db );
        
        $this->num_queries++; //更新查询次数
        $this->queries[] = $query;
        
        
        if ( $db->error ) {
            return false;
        }
        
        if ( preg_match( "/^\\s*(insert|delete|update|replace|alter) /i", $query ) ) {
            $this->rows_affected = $db->affected_rows;//mysql_affected_rows( $db );
            
            // Take note of the insert_id
            if ( preg_match( "/^\\s*(insert|replace) /i", $query ) ) {
                $this->insert_id = $db->insert_id;//mysql_insert_id($db);
            }
            
            // Return number of rows affected
            $return_val = $this->rows_affected;
        } else {
            /* 
            $i = 0;
            while ( $i < @mysql_num_fields( $this->result ) ) {
                $this->col_info[$i] = @mysql_fetch_field( $this->result );
                $i++;
            }
            */
            
            /* 获取所有的记录，改用新版本的方法
            $num_rows = 0;
            while ( $row = @mysql_fetch_assoc( $this->result ) ) {
                $this->last_result[$num_rows] = $row;
                $num_rows++;
            }
            */
            
            
            // Log number of rows the query returned
            // and return number of rows selected
            $this->num_rows = $return_val = $result->num_rows;
            $this->last_result = $this->_fetch_all($result);
            
            //@mysql_free_result( $this->result );
            @$result->free();
            
        }

        return $return_val;
    }
    
    private function flush(){
        $this->last_result = array();
        $this->num_rows = 0;
    }
    
    private function _fetch_all($result){
        if ($this->num_rows > 0){
            if (function_exists('mysqli_fetch_all')){
                return mysqli_fetch_all($result, MYSQLI_ASSOC);
            }else{
                $new_array = array();
                for ($i = 0; $i < $this->num_rows; $i++){
                    $new_array[] = mysqli_fetch_assoc($result);
                }
                return $new_array;
            }
        }else{
            return array();
        }
    }
    
    /**
    * Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
    *
    * The following directives can be used in the query format string:
    *   %d (decimal number)
    *   %s (string)
    *   %% (literal percentage sign - no argument needed)
    *
    * Both %d and %s are to be left unquoted in the query string and they need an argument passed for them.
    * Literals (%) as parts of the query must be properly written as %%.
    *
    * This function only supports a small subset of the sprintf syntax; it only supports %d (decimal number), %s (string).
    * Does not support sign, padding, alignment, width or precision specifiers.
    * Does not support argument numbering/swapping.
    *
    * May be called like {@link http://php.net/sprintf sprintf()} or like {@link http://php.net/vsprintf vsprintf()}.
    *
    * Both %d and %s should be left unquoted in the query string.
    *
    * <code>
    * wpdb::prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d", 'foo', 1337 )
    * wpdb::prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
    * </code>
    *
    * @link http://php.net/sprintf Description of syntax.
    * @since 2.3.0
    *
    * @param string $query Query statement with sprintf()-like placeholders
    * @param array|mixed $args The array of variables to substitute into the query's placeholders if being called like
    *   {@link http://php.net/vsprintf vsprintf()}, or the first variable to substitute into the query's placeholders if
    *   being called like {@link http://php.net/sprintf sprintf()}.
    * @param mixed $args,... further variables to substitute into the query's placeholders if being called like
    *   {@link http://php.net/sprintf sprintf()}.
    * @return null|false|string Sanitized query string, null if there is no query, false if there is an error and string
    *   if there was something to prepare
    */
    function prepare( $query = null ) {
        // ( $query, *$args )
        if ( is_null( $query ) )
            return;
    
        $args = func_get_args();
        array_shift( $args );
        // If args were passed as an array (as in vsprintf), move them up
        if ( isset( $args[0] ) && is_array($args[0]) )
        $args = $args[0];
        $query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
        $query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
        $query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
        array_walk( $args, array( &$this, 'escape_by_ref' ) );
        return @vsprintf( $query, $args );
    }
    
    /**
    * Escapes content for insertion into the database using addslashes(), for security.
    *
    * Works on arrays.
    *
    * @since 0.71
    * @param string|array $data to escape
    * @return string|array escaped as query safe string
    */
    function escape( $data ) {
        if ( is_array( $data ) ) {
            foreach ( (array) $data as $k => $v ) {
                if ( is_array( $v ) )
                    $data[$k] = $this->escape( $v );
                else
                    $data[$k] = $this->_weak_escape( $v );
            }
        } else {
            $data = $this->_weak_escape( $data );
        }
    
        return $data;
    }
    
    /**
    * Escapes content by reference for insertion into the database, for security
    *
    * @uses wpdb::_real_escape()
    * @param string $string to escape
    * @return void
    */
    function escape_by_ref( &$string ) {
        $string = $this->_real_escape( $string );
    }
    
    /**
    * Weak escape, using addslashes()
    *
    * @see addslashes()
    * @since 2.8.0
    * @access private
    *
    * @param string $string
    * @return string
    */
    private function _weak_escape( $string ) {
        return addslashes( $string );
    }
    
    /**
    * Real escape, using mysql_real_escape_string() or addslashes()
    *
    * @see mysql_real_escape_string()
    * @see addslashes()
    *
    * @param  string $string to escape
    * @return string escaped
    */
    private function _real_escape( $string ) {
        if ( $this->db )
            return $this->db->real_escape_string($string);
        else
            return addslashes( $string );
    }
    
    /**
    * Escape data. Works on arrays.
    *
    * @uses wpdb::_escape()
    * @uses wpdb::_real_escape()
    * @access private
    *
    * @param  string|array $data
    * @return string|array escaped
    */
    /*
    private function _escape( $data ) {
        if ( is_array( $data ) ) {
            foreach ( (array) $data as $k => $v ) {
                if ( is_array($v) )
                $data[$k] = $this->_escape( $v );
                else
                $data[$k] = $this->_real_escape( $v );
            }
        } else {
            $data = $this->_real_escape( $data );
        }
    
        return $data;
    }
    */
    
    function print_error( $str = '' ) {
        if ($this->debug === true){ print_r('<p>'.$str.'</p>'); }
    }
    
    /**
    * Helper function for insert and replace.
    *
    * Runs an insert or replace query based on $type argument.
    *
    * @access private
    * @since 3.0.0
    * @see wpdb::prepare()
    * @see wpdb::$field_types
    * @see wp_set_wpdb_vars()
    *
    * @param string $table table name
    * @param array $data Data to insert (in column => value pairs).  Both $data columns and $data values should be "raw" (neither should be SQL escaped).
    * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
    *   A format is one of '%d', '%s' (decimal number, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
    * @return int|false The number of rows affected, or false on error.
    */
    private function _insert_replace_helper( $table, $data, $format = null, $type = 'INSERT' ) {
        if ( ! in_array( strtoupper( $type ), array( 'REPLACE', 'INSERT' ) ) )
            return false;
        $formats = $format = (array) $format;
        $fields = array_keys( $data );
        $formatted_fields = array();
        foreach ( $fields as $field ) {
            if ( !empty( $format ) )
                $form = ( $form = array_shift( $formats ) ) ? $form : $format[0];
            elseif ( isset( $this->field_types[$field] ) )
                $form = $this->field_types[$field];
            else
                $form = '%s';
            $formatted_fields[] = $form;
        }
        $sql = "{$type} INTO `$table` (`" . implode( '`,`', $fields ) . "`) VALUES ('" . implode( "','", $formatted_fields ) . "')";
        //echo $this->prepare( $sql, $data );
        //exit;
        return $this->query( $this->prepare( $sql, $data ) );
    }
    
    /**
    * Insert a row into a table.
    *
    * <code>
    * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 'bar' ) )
    * wpdb::insert( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( '%s', '%d' ) )
    * </code>
    *
    * @since 2.5.0
    * @see wpdb::prepare()
    * @see wpdb::$field_types
    * @see wp_set_wpdb_vars()
    *
    * @param string $table table name
    * @param array $data Data to insert (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
    * @param array|string $format Optional. An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
    *   A format is one of '%d', '%s' (decimal number, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
    * @return int|false The number of rows inserted, or false on error.
    */
    function insert( $table, $data, $format = null ) {
        return $this->_insert_replace_helper( $table, $data, $format, 'INSERT' );
    }
    
    /**
    * Update a row in the table
    *
    * <code>
    * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 'bar' ), array( 'ID' => 1 ) )
    * wpdb::update( 'table', array( 'column' => 'foo', 'field' => 1337 ), array( 'ID' => 1 ), array( '%s', '%d' ), array( '%d' ) )
    * </code>
    *
    * @since 2.5.0
    * @see wpdb::prepare()
    * @see wpdb::$field_types
    * @see wp_set_wpdb_vars()
    *
    * @param string $table table name
    * @param array $data Data to update (in column => value pairs). Both $data columns and $data values should be "raw" (neither should be SQL escaped).
    * @param array $where A named array of WHERE clauses (in column => value pairs). Multiple clauses will be joined with ANDs. Both $where columns and $where values should be "raw".
    * @param array|string $format Optional. An array of formats to be mapped to each of the values in $data. If string, that format will be used for all of the values in $data.
    *   A format is one of '%d', '%s' (decimal number, string). If omitted, all values in $data will be treated as strings unless otherwise specified in wpdb::$field_types.
    * @param array|string $format_where Optional. An array of formats to be mapped to each of the values in $where. If string, that format will be used for all of the items in $where.  A format is one of '%d', '%s' (decimal number, string).  If omitted, all values in $where will be treated as strings.
    * @return int|false The number of rows updated, or false on error.
    */
    function update( $table, $data, $where, $format = null, $where_format = null ) {
        if ( ! is_array( $data ) || ! is_array( $where ) )
        return false;
    
        $formats = $format = (array) $format;
        $bits = $wheres = array();
        foreach ( (array) array_keys( $data ) as $field ) {
            if ( !empty( $format ) )
            $form = ( $form = array_shift( $formats ) ) ? $form : $format[0];
            elseif ( isset($this->field_types[$field]) )
            $form = $this->field_types[$field];
            else
            $form = '%s';
            $bits[] = "`$field` = {$form}";
        }
    
        $where_formats = $where_format = (array) $where_format;
        foreach ( (array) array_keys( $where ) as $field ) {
            if ( !empty( $where_format ) )
            $form = ( $form = array_shift( $where_formats ) ) ? $form : $where_format[0];
            elseif ( isset( $this->field_types[$field] ) )
            $form = $this->field_types[$field];
            else
            $form = '%s';
            $wheres[] = "`$field` = {$form}";
        }
    
        $sql = "UPDATE `$table` SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres );
        return $this->query( $this->prepare( $sql, array_merge( array_values( $data ), array_values( $where ) ) ) );
    }
    
    /**
    * Retrieve one row from the database.
    *
    * Executes a SQL query and returns the row from the SQL result.
    *
    * @since 0.71
    *
    * @param string|null $query SQL query.
    * @param string $output Optional. one of ARRAY_A | ARRAY_N | OBJECT constants. Return an associative array (column => value, ...),
    *   a numerically indexed array (0 => value, ...) or an object ( ->column = value ), respectively.
    * @param int $y Optional. Row to return. Indexed from 0.
    * @return mixed Database query result in format specifed by $output or null on failure
    */
    public function get_row( $query = null, $output = MYSQL_ASSOC, $y = 0 ) {
        //日志记录
        $this->func_call = "\$db->get_row(\"$query\",$output,$y)";
        if ( $query )
            $this->query( $query );
        else
            return null;
    
        if ( !isset( $this->last_result[$y] ) )
            return null;
    
        if ( $output == MYSQLI_ASSOC ) {
            return $this->last_result[$y] ? $this->last_result[$y] : null;
        } elseif ( $output == MYSQLI_NUM ) {
            return $this->last_result[$y] ? array_values(  $this->last_result[$y] ) : null;
        } else {
            $this->print_error(' $db->get_row(string query, output type, int offset) -- ');
        }
    }
    
    
    /**
    * Retrieve one column from the database.
    *
    * Executes a SQL query and returns the column from the SQL result.
    * If the SQL result contains more than one column, this function returns the column specified.
    * If $query is null, this function returns the specified column from the previous SQL result.
    *
    * @since 0.71
    *
    * @param string|null $query Optional. SQL query. Defaults to previous query.
    * @param int $x Optional. Column to return. Indexed from 0.
    * @return array Database query result. Array indexed from 0 by SQL result row number.
    */
    function get_col( $query = null , $x = 0 ) {
        if ( $query )
            $this->query( $query );
    
        $new_array = array();
        // Extract the column values
        for ( $i = 0, $j = count( $this->last_result ); $i < $j; $i++ ) {
            $new_array[$i] = $this->get_var( null, $x, $i );
        }
        return $new_array;
    }
    
    /**
    * Retrieve one variable from the database.
    *
    * Executes a SQL query and returns the value from the SQL result.
    * If the SQL result contains more than one column and/or more than one row, this function returns the value in the column and row specified.
    * If $query is null, this function returns the value in the specified column and row from the previous SQL result.
    *
    * @since 0.71
    *
    * @param string|null $query Optional. SQL query. Defaults to null, use the result from the previous query.
    * @param int $x Optional. Column of value to return.  Indexed from 0.
    * @param int $y Optional. Row of value to return.  Indexed from 0.
    * @return string|null Database query result (as string), or null on failure
    */
    function get_var( $query = null, $x = 0, $y = 0 ) {
        $this->func_call = "\$db->get_var(\"$query\", $x, $y)";
        
        if ( $query )
            $this->query( $query );
        
//      print_r($this->last_result);exit;
        
        // Extract var out of cached results based x,y vals
        if ( !empty( $this->last_result[$y] ) ) {
//          $values = $this->last_result[$y];
            $values = array_values( $this->last_result[$y] );
        }
        
//      echo '<pre>';print_r($values);echo '</pre>';exit;
        
        // If there is a value return it else return null
        return ( isset( $values[$x] ) && $values[$x] !== '' ) ? $values[$x] : null;
    }
    
    /**
    * 返回一个 MYSQLI_ASSOC 数据库记录集
    * @return mixed 一个数组记录集，空结果为 null|array()
    */
    function get_results( $query = null ) {
        //$this->func_call = "\$db->get_results(\"$query\", $output)";
    
        if ( $query )
            $this->query( $query );
        else
            return null;
        
        return $this->last_result;
        /*
        $new_array = array();
        if ( $output == OBJECT ) {
            // Return an integer-keyed array of row objects
            return $this->last_result;
        } elseif ( $output == OBJECT_K ) {
            // Return an array of row objects with keys from column 1
            // (Duplicates are discarded)
            foreach ( $this->last_result as $row ) {
                $key = array_shift( get_object_vars( $row ) );
                if ( ! isset( $new_array[ $key ] ) )
                $new_array[ $key ] = $row;
            }
            return $new_array;
        } elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
            // Return an integer-keyed array of...
            if ( $this->last_result ) {
                foreach( (array) $this->last_result as $row ) {
                    if ( $output == ARRAY_N ) {
                        // ...integer-keyed row arrays
                        $new_array[] = array_values( get_object_vars( $row ) );
                    } else {
                        // ...column name-keyed row arrays
                        $new_array[] = get_object_vars( $row );
                    }
                }
            }
            return $new_array;
        }
        return null;
        */
    }
}